﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace Red.GameData.Raw
{
    public partial class DataObject : Red.Core.DataConsistency.ConsistentObject
    {
        /// <summary>
        /// Object ID - the magic value that binds the whole stuff in here
        /// </summary>
        public Guid ID { get; private set; } // set only once

        /// <summary>
        /// Object ID of the base object from which derived props are taken
        /// </summary>
        public Guid BaseID { get; private set; } // set only once

        /// <summary>
        /// Object ID of the parent object, every nameless object MUST have a parent object specified
        /// </summary>
        public Guid ParentID { get; private set; } // set only once

        /// <summary>
        /// Name of the object's class
        /// NOTE: it may be empty (then it must be specified in one of the base objects)
        /// </summary>
        public string ClassName { get; private set; } // set only once

        /// <summary>
        /// Get the file this objects belongs to
        /// NOTE: file may be changed
        /// </summary>
        public DataFile File { get { return _file; } }

        /// <summary>
        /// Was this object modified since it was loaded ?
        /// </summary>
        public bool IsModified { get { return _isModified; } }

        /// <summary>
        /// Is this data object considered abstract? (we won't be able to instance it directly)
        /// </summary>
        public bool IsAbstract { get { return _isAbstract; } }

        /// <summary>
        /// Get the list of property value holders for this object
        /// NOTE: only overridden properties have the value holders
        /// </summary>
        public List<DataProperty> Properties { get { return _properties; } }// local values for this object only

        // local hack :( 
        public DataObject _TempshitBaseObject;

        /// <summary>
        /// Initialize a new data object
        /// </summary>
        /// <param name="file">Owning file</param>
        /// <param name="objectId">ID for the object, MUST NOT BE AN EMPTY GUID</param>
        /// <param name="baseId">Id of the base object</param>
        /// <param name="className">Class of the object</param>
        public DataObject(DataFile file, Guid objectId, Guid baseId, Guid parentId, string className)
        {
            Debug.Assert(className != "" || baseId != Guid.Empty); // we need at least one
            Debug.Assert(objectId != Guid.Empty);

            // THE CORE
            ID = objectId;
            BaseID = baseId;
            ParentID = parentId;
            ClassName = className; // may be empty -> derived from base

            // File this object is from
            Debug.Assert(file != null); // file should be specified
            _file = file;

            // stuff
            _properties = new List<DataProperty>();
            _isModified = false;
            _isAbstract = false;
        }

        /// <summary>
        /// Move this object to another file
        /// Warning: can cause data loss if used without care
        /// </summary>
        /// <param name="otherFile">The file to move the object to</param>
        public void MoveToFile(DataFile otherFile)
        {
            if (_file != otherFile)
            {
                // note: we mark both files are modified

                _file.ObjectsInFile.Remove(this);
                _file.MarkModified();

                _file = otherFile;

                _file.ObjectsInFile.Add(this);
                _file.MarkModified();
            }
        }

        /// <summary>
        /// Mark this object (and the owning file of course) as modified
        /// This is obviously a "tribute" to old CObject MarkModified function :)
        /// </summary>
        public void MarkModified()
        {
            _isModified = true;
            _file.MarkModified();

            // make sure any editor cached values will be updated
            InvalidateConsistencyMarker();
        }

        /// <summary>
        /// Unlink this object from it's parent
        /// Permanent operation, used during deletion
        /// </summary>
        public void UnlinkFromParent()
        {
            ParentID = Guid.Empty;
        }

        /// <summary>
        /// Link this object to it's new parent
        /// Permanent operation, used during attaching to the new parent
        /// </summary>
        public void LinkWithParent( Guid parentGuid )
        {
            ParentID = parentGuid;
        }

        /// <summary>
        /// Get property defined at this object scope, if specified property is not found it's created
        /// </summary>
        /// <param name="name">The name of the property to retrieve/create</param>
        /// <returns>Returns the property values holder for given name</returns>
        public DataProperty GetProperty(string name)
        {
            foreach (var prop in _properties)
            {
                if (prop.Name == name)
                    return prop;
            }

            DataProperty newProp = new DataProperty(this, name, false, false);
            _properties.Add(newProp);
            MarkModified(); // new property was created, mark the object as modified
            return newProp;
        }

        /// <summary>
        /// Get a value for the property but ONLY in case there's only one value there
        /// This is a very picky function and should not be used in general cases unless 100% sure that there's will be only one value
        /// </summary>
        /// <param name="name">The name of the property to get the value from</param>
        /// <returns>Retrieved single value of the property or an empty string if there's no property, property has no or more than one value</returns>
        public string GetPropertyValueSingle(string name)
        {
            var prop = GetPropertyNoCreate(name);
            if (prop != null)
            {
                // ONLY ONE VALUE IS ALLOWED
                if (prop.Values.Count == 1)
                {
                    var value = prop.Values[0];
                    if (value != null)
                    {
                        return value.Value;
                    }
                }
            }

            // well, no property, no value or more than one value
            return "";
        }

        /// <summary>
        /// Get property defined at this object scope - do not create missing properties
        /// </summary>
        /// <param name="name">Name of the property to find</param>
        /// <returns>The property of null if property was not found</returns>
        public DataProperty GetPropertyNoCreate(string name)
        {
            foreach (var prop in _properties)
            {
                if (prop.Name == name)
                    return prop;
            }

            // property was not found
            return null;
        }

        /// <summary>
        /// Remove property defined at this object's scope.
        /// This is equivalent of resetting the value of the property to whatever is coming from the base object
        /// </summary>
        /// <param name="name">Name of the proeprty to reset</param>
        /// <returns>Returns true if the property was found and removed, false if there's was no such property</returns>
        public bool RemoveProperty(string name)
        {
            foreach (var prop in _properties)
            {
                if (prop.Name == name)
                {
                    _properties.Remove(prop);
                    
                    // make sure the object is marked as modified
                    MarkModified();
                    return true;
                }
            }

            // no such property found
            return false;
        }

        /// <summary>
        /// Get the actual class name (from hierarchy)
        /// NOTE: this requires searching through the object collection and therefore this is NOT the fast function
        /// </summary>
        public string ActualClassName
        {
            get
            {
                return Utils.GetObjectClassName(this);
            }
        }

        /// Extract object stack
        /// TODO: it would be nice to search only once
        public DataObject[] ObjectStack
        {
            get
            {
                var temp = new DataObject[64];

                // extract stack depth
                int depth = 0;
                {
                    var cur = this;
                    while (cur != null)
                    {
                        // store
                        temp[depth] = cur;
                        depth += 1;

                        // TODO: resolve virtual bases

                        // go to base
                        cur = _file.Collection.Find(cur.BaseID);
                    }
                }

                // create return list
                var ret = new DataObject[ depth ];

                // extract object in proper stack order
                for (int i = 0; i < depth; ++i)
                    ret[i] = temp[depth - 1 - i]; 

                return ret;
            }
        }

        /// <summary>
        /// List of property value overrides for THIS OBJECT (may be empty, never NULL)
        /// </summary>
        private List<DataProperty> _properties; // local values for this object only

        /// <summary>
        /// Was this object modified since it was loaded
        /// This is set in MarkModified and unset when the file is successfully saved
        /// </summary>
        private bool _isModified;

        /// <summary>
        /// Flag symbolizing that the object is considered abstract and should not be EVER instanced directly
        /// </summary>
        private bool _isAbstract;

        /// <summary>
        /// File this object is coming from
        /// </summary>
        private DataFile _file;
    }


}
